﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace KNN
{
    class KNN
    {
        private static double[][] _Vector;
        private static int[] _Class;
        private static List<int> _ClassList;
        private static int _QuantityOfTester;
        private static int _Radius;
        public static double Distance(int first, int second)
        {
            //wektory to lista przechowujaca wektory
            //wektory[i] to konkretny wektor
            //wektory[i][j] gdzie j<wektory[i].length to kolejne wartosci x, a ostatnia cyfra to Y
            //pierwszy to numer indeksu pierwszego wektora
            //drugi to numer indeksu drugiego wektora
            /*
             * funkcja bedzie obliczac odleglosc wektora[pierszy] od wektora[drugi]
             * funkcja będzie zwracac właśnie tą odległość
            */
            double scalar = 0;//zmienna pomocnicza, sluzy do przechowywania poprzednich wartosci
            for (int i = 0; i < _Vector[first].Length; i++) //petla przechodzi po wszystkich skalarach wektora, nie wchodzi tylko na Y w konretnym wektorze
                scalar += Math.Pow(_Vector[first][i] - _Vector[second][i], 2);//korzystam ze wzoru (x1-x2)^2+(y1-y2)^2... gdzie potega jak i ilosc zmiennych są zmienne
            return Math.Pow(scalar, 0.5); //gdy otrzymamy juz (x1-x2)^2+(y1-y2)^2... musimy wyciagnac z tego pierwiastek stopnia takiego, ile jest róznych x
        }
        public static void SetTrainer(Neighbour[] neighbour, int trainer, double distance)
        {
            //tablica trzyma wszystkich testujacych oraz posortowanych
            //tablica[i] to grupa testujacych dla uczacego o indeksie i
            //tablica[i][j] to konkretny testujacy dla uczacego
            //tablica[i][j].Index to indeks konkretnego testujacego, który ma odwzwierciedlenie w tablicy/liście z wektorami
            //tablica[i][j].Waga to waga konkretnego testujacego, czyli w naszym przypadku odleglosc uczacego i od testujacego tablica[i][j].Index
            //testowy to indeks testowego, który ma odzwierciedlenie w tablicy/liście z wektorami
            //uczacy to indeks uczacego, który ma odzwierciedlenie w tablicy/liście z wektorami
            //ilosc to informacja jak liczna jest grupa najbliższych dla uczacej, jesli będzie 9, to każdej uczącej zostanie przypisane max 9 testujących
            //odleglosc to odleglosc wektora[testowy] od wektora[uczacy]
            /*
             * funkcja ma za zadanie dopasować testowego do grupy najblizszych uczącego jeśli jest taka możliwość
             * funkcja operuje cały czas na tablicy z testującymi także nic nie zwraca
             */
            //sprawdzam tutaj czy w grupie najbliższych testowych jest choćby jedno miejsce wolne, 
            //moge tak to sprawdzić ponieważ testowe są wpisane do tej tablicy od najbliższego do najdalszego,
            //czyli na ostatnim miejscu jest najdalszy
            //Console.WriteLine("odlegosc:"+distance);
            if(neighbour[_Radius-1].Weight > distance){
                for (int i = 0; i < _Radius; i++) //funkcja przechodzi po kazdym testowym w grupie najblizszych
                    if (neighbour[i].Weight > distance) // warunek sprawdza czy w miejscu i, nie ma miejsca czasami dla testowego
                    {                                        //jesli warunek sie sprawdzi to bedziemy przesuwac od tego miejsca w tył wszystkie inne testowe, tak aby ten który był ostatni wypadł całkiem z tablicy
                        for (int j = _Radius - 1; j > i; j--)
                        {
                            neighbour[j].Index = neighbour[j - 1].Index;
                            neighbour[j].Weight = neighbour[j - 1].Weight;

                        }
                        neighbour[i].Index = trainer; //wpisanie w miejscu i indeksu testowego
                        neighbour[i].Weight = distance;//wpisanie w miejscu i wagi(odleglosc) testowego
                        break;//break nam przerywa pętle z inkrementacją i, jest to jednoczesnie ostatnia czynność w tej funkcji
                    }
            }
        }


        public static Neighbour[] FindNearestNeighbors(int tester)
        {

            //wektory to lista przechowujaca wszystkie wektory
            //wektory[i] to konkretny wektor
            //wektory[i][j] gdzie j<wektory[i].length to kolejne wartosci dla x w wektorze, ostatnie miejsce dla j to Y
            //punkt_podzialu to informacja o punkcie gdzie znajduja sie uczace<0,punkt_podziau) oraz testujących<punkt_podzialu,wektory.Count)
            //ilosc_najblizszych to informacja o liczności grupy najbliższych wektorów, jesli będzie równa 9, to dla każdego uczącego zostanie przypisanych 9 najbliższych testujących
            /* 
             * funkcja będzie miała za zadanie stworzyć tablice najbliższych oraz przypisać do tablicy najbliższych testujacych dla każdego uczącego
             * funkcja zwraca tablice już utworzoną i wypełnioną testowanymi dla każdego uczącego, jednocześnie tablica jest posortowana tak że każda grupa
             * testowanych dla konkretnej uczącej jest od najbliższego do najdalszego. grupa jest liczności <ilosc_najblizszych>
            */


            Neighbour[] neighbour;//tworzymy tablice w której będą zapisane grupy dla każdego uczącego
            neighbour = new Neighbour[_Radius]; // tworzymy kolumny w tablicy //tworzymy wiersze w tablicy
            for (int i = 0; i < _Radius; i++)
                neighbour[i] = new Neighbour(-1, double.PositiveInfinity);//tworzymy kazdy obiekt po kolei, i przypisujemy mu w wadze nieskonczonosc, im mniejsza waga tym testowy bardziej korzystny.
            //tak więc nieskonczoność jest równoznaczna z brakiem wartości testowanego dla uczacego
            for (int trainer = _QuantityOfTester; trainer < _Vector.GetLength(0); trainer++) // testowy to numer indeksu testowego
                SetTrainer(neighbour, trainer, Distance(tester, trainer)); //funkcja będzie starała się umieścić testowego w grupie najbliższych uczącego o indeksie uczacy
            for (int i = 0; i < 9; i++)
            {
                //Console.WriteLine("Numer sasiada     :" + i);
                //Console.WriteLine("Index sasiada     :" + neighbour[i].Index);
                //Console.WriteLine("Waga sasiada      :" + neighbour[i].Weight);
                //Console.WriteLine("klasa sasiada     :" + _Class[neighbour[i].Index]);
                //Console.WriteLine("x1                :" + _Vector[neighbour[i].Index][0]);
                //Console.WriteLine("x2                :" + _Vector[neighbour[i].Index][1]);
                //Console.WriteLine("x3                :" + _Vector[neighbour[i].Index][2]);
                //Console.WriteLine("x4                :" + _Vector[neighbour[i].Index][3]);
                //Console.WriteLine();

            }
            //Console.ReadKey();
            return neighbour;//zwraca nam tablice już wypełnioną i posortowaną
        }
        public static void Shuffling()
        {
            //wektory to lista wszystkich wektorów
            //wektory[i] to konkretny wektor o indeksie i
            //wektory[i][j] gdzie j<wektory[i].length to wartości współrzędnych wektora, natomiast na ostatnim miejscu jest Y
            //podzial to informacja o podziale wektórów na uczace<0,podzial) i testujące<podzial,wektory.Count)
            /*
             * funkcja ma za zadanie wybrać z grupy wszystkich wektorów wektory które będą od tego momentu uważane za uczace
             * oraz umieścić je na początku tablicy, w taki sposób że uczace będą miały indeksy <0,punkt) natomiast testowe <punkt, wektory.Count)
             * funkcja zwraca nam informacje o tym gdzie znajduje się punkt podziału na dwie grupy
            */
            double[] handler; //wskaźnik pomocniczy, będzie służył do przeżucania losowych wektorów na początek tablicy
            Random R = new Random();
            int tester;//zmienna pomocnicza, będzie trzymać wylosowaną liczbę
            for (int i = 0; i < _QuantityOfTester; i++) //pętla będzie chodzić tak długo jak w tablicy nie zostaną wybrane uczace a raczej <podzial> uczacych
            {
                tester = R.Next(i + 1, _Vector.Length); // losowanie liczby z zakresu (i,wektory.Count), to liczba aktualnie dostępnych testowych, jednoczenie wylosowana liczba nie będzie zachaczać o grupe uczących, tylko będzie operować na indeksach testujących
                handler = _Vector[tester];//zamiana wektora losowy z wektorem i, krok 1/3
                _Vector[tester] = _Vector[i];//zamiana wektora losowy z wektorem i, krok 2/3
                _Vector[i] = handler;//zamiana wektora losowy z wektorem i, krok 3/3
            }
        }
        public static void CountClassAmount() {//funkcja liczy ile jest roznych klas do klasyfikacji
            bool test;
            _ClassList = new List<int>();
            for (int i = 0; i < _Vector.GetLength(0); i++) {
                test = false;
                for (int j = 0; j < _ClassList.Count; j++)
                    if (_Class[i] == _ClassList[j]) test = true;
                if (test == false) _ClassList.Add(_Class[i]);
            }
        }
        public static int Common(Neighbour[] neighbor)
        {
            for (int i = 0; i < 9; i++)
            {
                //Console.WriteLine("Numer sasiada     :" + i);
                //Console.WriteLine("Index sasiada     :" + neighbor[i].Index);
                //Console.WriteLine("Waga sasiada      :" + neighbor[i].Weight);
                //Console.WriteLine("klasa sasiada     :" + _Class[neighbor[i].Index]);
                //Console.WriteLine("x1                :" + _Vector[neighbor[i].Index][0]);
                //Console.WriteLine("x2                :" + _Vector[neighbor[i].Index][1]);
                //Console.WriteLine("x3                :" + _Vector[neighbor[i].Index][2]);
                //Console.WriteLine("x4                :" + _Vector[neighbor[i].Index][3]);
                //Console.WriteLine();

            }
            int[] MostCommonClass = new int[_ClassList.Count];
            for (int i = 0; i < _ClassList.Count; i++)
                MostCommonClass[i] = 0;
            for (int i = 0; i < neighbor.GetLength(0); i++)
                if (neighbor[i].Weight != double.PositiveInfinity)
                {
                    int k = neighbor[i].Index;//k oznacza indeks testowego dla uczacej o indeksie i
                    int l = _ClassList.FindIndex(s => s == _Class[k]);//znajduje index klasy aktualnej prze zpetle j
                    MostCommonClass[l]++;//licznik ktory Y w danej grupie testowych
                }
            //bede teraz wybieral ktory element w zbiorze najczestszych jest najliczniejszy, czyli ktorych Y jest najwiecej
            int max_i = 0;
            int max = MostCommonClass[0];
            for (int j = 0; j < _ClassList.Count; j++)
                if (MostCommonClass[j] > max)
                {   
                    max_i = j;
                    max = MostCommonClass[j];
                }
            //Console.WriteLine("Klasa najczestsza :" + _ClassList[max_i]);
            //Console.ReadKey();
            //Console.WriteLine("______________________");
            return _ClassList[max_i];
        }
        public KNN(string path)//otwiera plik i zczytuje dane(potem i tak bedzie baza danych sql)
        {
            string[] AlLines = File.ReadAllLines(path);
            _Vector = new double[AlLines.Length][];
            _Class = new int[AlLines.Length];
            for (int y = 0; y < AlLines.Length; y++)
            {
                string[] Line = AlLines[y].Split(new string[] { " " },
                       StringSplitOptions.RemoveEmptyEntries);
                _Vector[y] = new double[Line.Length - 1];
                _Class[y] = Convert.ToInt32(Line.Last());
                for (int x = 0; x < Line.Length - 1; x++)
                    _Vector[y][x] = Convert.ToDouble(Line[x]);
            }
            CountClassAmount();
            _QuantityOfTester = 0;
        }
        public bool IsEqual(int multi, int single)
        {
            for (int i = 1; multi > 0; multi = multi / 2)
            {
                if (single == multi % 2 * i)
                    return true;
                i *= 2;
            }
            return false;
        }
        public double Create(int quantity, int radius) //tworzy liste dokladnosci dla danego procenta, procent oznacza ilsoc uczacych 
        {
            _QuantityOfTester = quantity;
            _Radius = radius;
            double precision = 0;
            Shuffling();
            Neighbour[][] neighbour = new Neighbour[_QuantityOfTester][];
            for (int i = 0; i < _QuantityOfTester; i++)
                neighbour[i] = FindNearestNeighbors(i);//glowna funkcja, znajdujemy oraz przyporzadkowujemy <ilosc_najblizszych> testowych
            for (int i = 0; i < _QuantityOfTester; i++)
            {
                //Console.WriteLine("Numer testowego     :" + i);
                //Console.WriteLine("Klasa testowego     :" + _Class[i]);
                //Console.WriteLine("x1                  :" + _Vector[i][0]);
                //Console.WriteLine("x2                  :" + _Vector[i][1]);
                //Console.WriteLine("x3                  :" + _Vector[i][2]);
                //Console.WriteLine("x4                  :" + _Vector[i][3]);
                //Console.WriteLine("--------------------------------");
                if ( IsEqual(Common(neighbour[i]),_Class[i])) precision++;
            }
            //Console.WriteLine("Dokladnosc:::>>>:::" + precision/_QuantityOfTester);
            precision /= _QuantityOfTester;
           
            return precision;
        }
        public double[] Create(bool[,] AG_Table)//tworzy tablice precyzji 
        {
            if (AG_Table.GetLength(0) == _QuantityOfTester)
            {
                double[] precision = new double[AG_Table.GetLength(0)];
                Neighbour[][] neighbour = new Neighbour[_QuantityOfTester][];
                int length;
                for (int i = 0; i < AG_Table.GetLength(0); i++)
                {
                    length = 0;
                    for (int j = 0; j < AG_Table.GetLength(1); j++)
                        if (AG_Table[i, j] == true)
                        {
                            neighbour[j] = FindNearestNeighbors(j);
                            length++;
                        }
                    precision[i] = 0;
                    for (int j = 0; j < AG_Table.GetLength(1); j++)
                        if (AG_Table[i, j] == true && Common(neighbour[j]) == _Class[j]) precision[i]++;
                    precision[i] /= length;

                }
                return precision;
            }
            else
            {
                //Console.WriteLine("Bledna ilosc testowych");
                //Console.ReadKey();
            }
            return null;
        }
        public int QuantityOfTester
        {
            get { return _QuantityOfTester; }
        }
        public int QuantityOfTrainer
        {
            get { return _Vector.Length - _QuantityOfTester; }
        }
        public int Radius
        {
            get { return _Radius; }
        }
       
    }
}
